﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace KS.DataWrapper.DataWrapper.LINQ
{
    internal class QueryTranslator : ExpressionVisitor
    {
        private StringBuilder sb;

        internal QueryTranslator() 
        { 
        }

        internal string Translate(Expression expression)
        {
            this.sb = new StringBuilder();
            this.Visit(expression);
            return this.sb.ToString();
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(Queryable) &&
                m.Method.Name == "Where")
            {
                this.sb.Append("SELECT * FROM (");
                this.Visit(m.Arguments[0]);
                this.sb.Append(") AS T WHERE ");
                LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
                this.Visit(lambda.Body);
                return m;
            }
            //throw new NotSupportedException(string.Format(
            //    "The method '{0}' is not supported", m.Method.Name));
            return null;
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {
            switch (u.NodeType)
            {
                case ExpressionType.Not:
                    this.sb.Append(" ! ");
                    this.Visit(u.Operand);
                    break;
                default:
                    throw new NotSupportedException(string.Format(
                        "The unary operator '{0}' is not supported", u.NodeType));
            }
            return u;
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            this.sb.Append("(");
            this.Visit(b.Left);
            switch (b.NodeType)
            {
                case ExpressionType.And:
                    this.sb.Append(" && ");
                    break;
                case ExpressionType.Or:
                    this.sb.Append(" ||");
                    break;
                case ExpressionType.Equal:
                    this.sb.Append(" == ");
                    break;
                case ExpressionType.NotEqual:
                    this.sb.Append(" != ");
                    break;
                case ExpressionType.LessThan:
                    this.sb.Append(" < ");
                    break;
                case ExpressionType.LessThanOrEqual:
                    this.sb.Append(" <= ");
                    break;
                case ExpressionType.GreaterThan:
                    this.sb.Append(" > ");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    this.sb.Append(" >= ");
                    break;
                //default:
                //    throw new NotSupportedException(
                //        string.Format("The binary operator '{0}' is not supported", b.NodeType));
            }
            this.Visit(b.Right);
            this.sb.Append(")");
            return b;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            IQueryable q = c.Value as IQueryable;
            if (q != null)
            {
                // assume constant nodes w/ IQueryables are table references
                //this.sb.Append("SELECT * FROM ");
                this.sb.Append(q.ElementType.Name);
            }
            else if (c.Value == null)
            {
                this.sb.Append("NULL");
            }
            else
            {
                this.sb.Append(c.Value);
                //switch (Type.GetTypeCode(c.Value.GetType()))
                //{
                //    case TypeCode.Boolean:
                //        this.sb.Append(((bool)c.Value) ? 1 : 0);
                //        break;
                //    case TypeCode.String:
                //        this.sb.Append("'");
                //        this.sb.Append(c.Value);
                //        this.sb.Append("'");
                //        break;
                //    case TypeCode.Object:
                //        throw new NotSupportedException(
                //            string.Format(
                //                "The constant for '{0}' is not supported", c.Value));
                //    default:
                //        this.sb.Append(c.Value);
                //        break;
                //}
            }
            return c;
        }

        protected Expression VisitMemberAccess(MemberExpression m)
        {
            if (m.Expression != null &&
                m.Expression.NodeType == ExpressionType.Parameter)
            {
                this.sb.Append(m.Member.Name);
                return m;
            }
            throw new NotSupportedException(
                string.Format("The member '{0}' is not supported", m.Member.Name));
        }

        private static Expression StripQuotes(Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            return e;
        }
    }
}